<?php
declare(strict_types=1);

namespace app\shop\service;

use app\shop\enums\coupon\CouponDiscountType;
use app\shop\enums\coupon\CouponRangeType;
use app\shop\enums\coupon\CouponStatus;
use app\shop\library\calculate\Calculate;
use app\shop\library\ShopAuth;
use app\shop\model\order\OrderModel;
use app\shop\model\user\UserCouponModel;
use Exception;
use think\db\BaseQuery;
use think\Model;

/**
 * @property UserCouponModel $userCouponModel
 */
#[\AllowDynamicProperties]
class CouponService
{
    public function __construct()
    {
        $this->userCouponModel = new UserCouponModel();

        $this->processUserExpiredCoupon();
    }

    /**
     * 处理用户过期优惠券
     * @return void
     */
    public function processUserExpiredCoupon(): void
    {
        $expiredCouponIds = $this->userCouponModel->where([
            ['status', '=', CouponStatus::TO_USE],
            ['end_time', '<', time()]
        ])->column('id');

        if ($expiredCouponIds) {
            $this->userCouponModel->update(['status' => CouponStatus::EXPIRED], ['id' => $expiredCouponIds]);
        }
    }

    /**
     * 获取用户优惠券列表
     * @param int|string $userId
     * @param array      $goodsIds
     * @param int        $rangeType
     * @return array
     */
    public function getCouponsByUserId(int|string $userId, array $goodsIds, int $rangeType): array
    {
        $query = UserCouponModel::getQuery()->with(['coupon']);

        $query->hasWhere('coupon', function ($query) use ($rangeType) {
            $query->where('status', 1)->where('range_type', $rangeType);
            if ($rangeType == CouponRangeType::RANGE_GOODS && !empty($goodsIds)) {
                $goodsIds = implode(',', $goodsIds);
                $query->whereFindInSet('range_goods_ids', $goodsIds);
            }
        });

        $query->where([
            ['user_id', '=', $userId],
            ['UserCouponModel.status', '=', CouponStatus::TO_USE]
        ])->whereBetweenTimeField('UserCouponModel.start_time', 'UserCouponModel.end_time');


        return UserCouponModel::listQuery($query);
    }


    /**
     * 获取用户拥有的全部商品的优惠券列表
     * @param int|string $userId
     * @return array
     */
    public function getAllGoodsCouponByUserId(int|string $userId): array
    {
        return $this->getCouponsByUserId($userId, [], CouponRangeType::ALL_GOODS);
    }

    /**
     * 获取用户拥有的抵扣部分商品的优惠券列表
     * @param int|string $userId
     * @param array      $goodsIds
     * @return array
     */
    public function getSomeGoodsCouponByUserId(int|string $userId, array $goodsIds): array
    {
        return $this->getCouponsByUserId($userId, $goodsIds, CouponRangeType::RANGE_GOODS);
    }

    /**
     * 获取用户可用优惠券列表
     * @param array $goodsIds
     * @param float $totalPrice
     * @return array
     */
    public function getUserAvailableCoupon(array $goodsIds, float $totalPrice): array
    {
        $userId = ShopAuth::instance()->id;

        $allGoodsCoupon  = $this->getAllGoodsCouponByUserId($userId);
        $someGoodsCoupon = $this->getSomeGoodsCouponByUserId($userId, $goodsIds);

        $couponList = array_merge($allGoodsCoupon, $someGoodsCoupon);

        $userCouponList = [];
        foreach ($couponList as &$coupon) {
            if ($totalPrice >= $coupon['min_price']) {
                switch ($coupon['discount_type']) {
                    case CouponDiscountType::DISCOUNT_AMOUNT:
                        $couponAmount = (string)$coupon['discount_amount'];

                        if (Calculate::newDecimal($couponAmount)->compGte($totalPrice)) {
                            $coupon['reduced_price'] = Calculate::newDecimal($totalPrice)->sub(0.01)->getValue();
                        } else {
                            $coupon['reduced_price'] = min($couponAmount, $totalPrice);
                        }
                        break;
                    case CouponDiscountType::DISCOUNT_RATIO:
                        $couponRatio             = 100 - ($coupon['discount_ratio'] * 100);
                        $coupon['reduced_price'] = Calculate::newDecimal($totalPrice)->mul($couponRatio)->div(100)->getValue();
                        break;
                }
                $userCouponList[] = $coupon;
            }
        }

        // 按照抵扣金额降序排序
        usort($userCouponList, function ($coupon1, $coupon2) {
            return $coupon2['reduced_price'] <=> $coupon1['reduced_price']; // 使用太空船操作符进行降序排序
        });

        return $userCouponList;
    }

    /**
     * 计算商品优惠券抵扣
     * @param array  $goods
     * @param float  $totalPrice
     * @param string $couponId
     * @return array
     * <br>['coupon_list' => [], 'coupon_discount' => '0']
     */
    public static function calculateCoupon(array &$goods, float $totalPrice, string $couponId): array
    {
        usort($goods, function ($goods1, $goods2) {
            return $goods2['total_price'] <=> $goods1['total_price'];
        });

        $goodsId = array_column($goods, 'goods_id');

        $couponList = (new self)->getUserAvailableCoupon($goodsId, $totalPrice);

        $actualCouponIndex = array_search($couponId, array_column($couponList, 'id'));
        if ($actualCouponIndex === false) {
            return [
                'coupon_list'     => $couponList,
                'coupon_discount' => '0.00'
            ];
        }

        // 实际使用的优惠券
        $actualCoupon = $couponList[$actualCouponIndex];
        // 实际能使用优惠券的商品列表
        $actualGoods = array_filter($goods, function ($item) use ($actualCoupon) {
            return $item['total_price'] >= $actualCoupon['min_price'];
        });
        // 实际能使用优惠券的商品总金额
        $actualTotalPrice = array_sum(array_column($actualGoods, 'total_price'));
        // 计算商品价格占比权重
        $totalPriceArr = array_column($actualGoods, 'total_price');

        // 实际抵扣的优惠券总金额
        $totalCouponDiscount = $actualCoupon['reduced_price'];
        if ($actualTotalPrice <= $totalCouponDiscount) {
            $couponList[$actualCouponIndex]['reduced_price'] = $totalCouponDiscount;
        }

        // 分配掉的优惠券抵扣金额
        $assignedCouponDiscount = $totalCouponDiscount;

        foreach ($goods as &$goodsItem) {
            $goodsItem['coupon_discount'] = '0.00';
            $goodsItem['discount_amount'] = '0.00';
            if ($goodsItem['total_price'] < $actualCoupon['min_price']) {
                continue;
            }
            $totalPrice               = (string)array_sum($totalPriceArr);
            $goodsItem['goods_weigh'] = Calculate::newDecimal($goodsItem['total_price'])->div($totalPrice, 6)->getValue();
            // 优惠券折扣分配
            $discountAmount = Calculate::newDecimal($totalCouponDiscount)->mul($goodsItem['goods_weigh'])->getValue();

            if (count($actualGoods) > 1) {
                $assignedCouponDiscount = Calculate::newDecimal($assignedCouponDiscount)->sub($discountAmount)->getValue();
                array_splice($actualGoods, 0, 1);
            } else {
                $discountAmount = $assignedCouponDiscount;
            }
            $goodsItem['coupon_discount'] = $discountAmount;
            $goodsItem['discount_amount'] = Calculate::newDecimal($goodsItem['discount_amount'])->add($discountAmount)->getValue();
            $goodsItem['pay_price']       = Calculate::newDecimal($goodsItem['total_price'])->sub($discountAmount)->getValue();
        }

        $couponDiscount = (string)array_sum(array_column($goods, 'coupon_discount'));

        return [
            'coupon_list'     => $couponList,
            'coupon_discount' => $couponDiscount
        ];
    }

    /**
     * 通过订单消费优惠券
     * @param string $couponId
     * @return array
     * @throws Exception
     */
    public function consumeByOrderId(string $couponId): array
    {
        // 参数验证
        if (empty($couponId)) {
            throw new Exception('优惠券ID不能为空');
        }
        $couponIds = explode(',', $couponId);
        $couponIds = array_filter($couponIds, 'strlen');
        if (empty($couponIds)) {
            throw new Exception('无效的优惠券ID列表');
        }

        $this->userCouponModel->startTrans();
        try {
            // 查询用户使用的优惠券详情
            $userCoupons = $this->userCouponModel->with(['coupon'])
                ->scope('effectiveTime')
                ->whereIn('id', $couponIds)
                ->select()
                ->toArray();
            // 更新优惠券状态
            $this->userCouponModel->update(['status' => CouponStatus::USED], ['id' => array_column($userCoupons, 'id')]);
            // 提交事务
            $this->userCouponModel->commit();
        } catch (Exception) {
            // 回滚事务
            $this->userCouponModel->rollback();
            throw new Exception('优惠券消费失败');
        }

        return $userCoupons;
    }

    /**
     * 通过优惠券ID获取用户优惠券详情
     * @param int|string $couponId
     * @param int        $userId
     * @return array|mixed|BaseQuery|Model
     */
    public function getUserCouponByCouponId(int|string $couponId, int $userId): mixed
    {
        return $this->userCouponModel
            ->where(['coupon_id' => $couponId, 'user_id' => $userId, 'status' => CouponStatus::TO_USE])
            ->with([
                'coupon' => function ($query) {
                    $query->where('status', 1);
                }
            ])
            ->findOrEmpty();
    }

    /**
     * 获取优惠券可用的商品ID范围
     * @param int|string $couponId
     * @param int        $userId
     * @return array
     */
    public function getCouponRangeGoodsIds(int|string $couponId, int $userId): array
    {
        if (!$userId) {
            return [];
        }
        $coupon = $this->getUserCouponByCouponId($couponId, $userId);
        if ($coupon->isEmpty()) {
            return [0];
        }
        if ($coupon->range_type == CouponRangeType::ALL_GOODS) {
            return [];
        }
        if ($coupon->range_type == CouponRangeType::RANGE_GOODS) {
            return $coupon->range_goods_ids;
        }
        return [0];
    }

    /**
     * 回退订单已使用的优惠券
     * @param int|string $orderId
     * @return true
     */
    public function rollbackByOrderId(int|string $orderId): bool
    {
        $orderModel = new OrderModel();

        $order      = $orderModel->where(['id' => $orderId])->field(['discount_detail'])->findOrEmpty()->toArray();
        $couponInfo = $order['discount_detail']['coupon_discount'] ?? [];

        if (!$couponInfo) {
            return true;
        }

        $couponIds = array_column($couponInfo, 'id');
        if (!$couponIds) {
            return true;
        }

        $this->userCouponModel
            ->scope('effectiveTime')
            ->whereIn('id', $couponIds)
            ->update([
                'status' => CouponStatus::TO_USE
            ]);

        return true;
    }
}